import cv2
import numpy as np
import win32gui
import win32con
import win32api
import ctypes
import win32ui
class GameCtrl:
    def __init__(self, hwnd):
        """
        初始化 GameCtrl 类，设置窗口句柄并获取屏幕宽度和高度。

        参数:
        hwnd (int): 窗口句柄。
        """
        try:
            # 初始化屏幕宽度和高度，并设置窗口句柄
            self.screen_width = win32api.GetSystemMetrics(0)
            self.screen_height = win32api.GetSystemMetrics(1)
            self.hwnd = hwnd  # 添加类成员变量 hwnd
        except Exception as e:
            print(f"Error initializing GameCtrl: {e}")

    def CaptureImg(self, x=None, y=None, width=None, height=None):
        """
        捕获指定窗口的图像。

        参数:
        x (int, optional): 截图区域左上角的 x 坐标。默认为 None，表示从窗口左上角开始。
        y (int, optional): 截图区域左上角的 y 坐标。默认为 None，表示从窗口左上角开始。
        width (int, optional): 截图区域的宽度。默认为 None，表示到窗口右下角。
        height (int, optional): 截图区域的高度。默认为 None，表示到窗口右下角。

        返回值:
        numpy.ndarray: 截图的图像数据，格式为 BGR。如果捕获失败，返回 None。
        """
        try:
            # 捕获指定窗口的图像
            hwnd = self.hwnd  # 使用类成员变量 hwnd
            left, top, right, bottom = win32gui.GetWindowRect(hwnd)
            if x is None:
                x = 0
            if y is None:
                y = 0
            if width is None or width==0:
                width = right - left - x
            if height is None or height==0:
                height = bottom - top - y

            hwndDC = win32gui.GetWindowDC(hwnd)
            mfcDC = win32ui.CreateDCFromHandle(hwndDC)
            saveDC = mfcDC.CreateCompatibleDC()

            saveBitMap = win32ui.CreateBitmap()
            saveBitMap.CreateCompatibleBitmap(mfcDC, width, height)

            saveDC.SelectObject(saveBitMap)

            saveDC.BitBlt((0, 0), (width, height), mfcDC, (x, y), win32con.SRCCOPY)

            bmpinfo = saveBitMap.GetInfo()
            bmpstr = saveBitMap.GetBitmapBits(True)
            img = np.frombuffer(bmpstr, dtype='uint8')
            img = img.reshape((bmpinfo['bmHeight'], bmpinfo['bmWidth'], 4))

            win32gui.DeleteObject(saveBitMap.GetHandle())
            saveDC.DeleteDC()
            mfcDC.DeleteDC()
            win32gui.ReleaseDC(hwnd, hwndDC)

            img = cv2.cvtColor(img, cv2.COLOR_BGRA2BGR)
            return img
        except Exception as e:
            print(f"Error capturing image: {e}")
            return None

    def Click(self, x, y):
        """
        在指定位置模拟鼠标点击。

        参数:
        x (int): 点击位置的 x 坐标。
        y (int): 点击位置的 y 坐标。
        """
        try:
            # 在指定位置模拟鼠标点击
            hwnd = self.hwnd  # 使用类成员变量 hwnd
            lParam = win32api.MAKELONG(x, y)
            win32api.SendMessage(hwnd, win32con.WM_LBUTTONDOWN, win32con.MK_LBUTTON, lParam)
            win32api.SendMessage(hwnd, win32con.WM_LBUTTONUP, 0, lParam)
        except Exception as e:
            print(f"Error clicking at ({x}, {y}): {e}")

    def SendKey(self, key, modifiers=[]):
        """
        模拟按键操作。

        参数:
        key (int): 按键的虚拟键码，例如 ord('A') 表示按键 'A'。
        modifiers (list, optional): 组合按键的虚拟键码列表。默认为空列表。
            示例:
            - 单个按键: `SendKey(ord('A'))`
            - 组合按键 (Ctrl+C): `SendKey(ord('C'), [win32con.VK_CONTROL])`
            - 组合按键 (Ctrl+Shift+A): `SendKey(ord('A'), [win32con.VK_CONTROL, win32con.VK_SHIFT])

        """
        try:
            # 模拟按键操作
            hwnd = self.hwnd  # 使用类成员变量 hwnd
            # 获取按键的扫描码
            MapVirtualKey = ctypes.windll.user32.MapVirtualKeyW
            scancode = MapVirtualKey(key, 0)

            for mod in modifiers:
                win32api.SendMessage(hwnd, win32con.WM_KEYDOWN, mod, MapVirtualKey(mod, 0) << 16)
            win32api.SendMessage(hwnd, win32con.WM_KEYDOWN, key, scancode << 16)
            win32api.SendMessage(hwnd, win32con.WM_KEYUP, key, scancode << 16)
            for mod in reversed(modifiers):
                win32api.SendMessage(hwnd, win32con.WM_KEYUP, mod, MapVirtualKey(mod, 0) << 16)
        except Exception as e:
            print(f"Error sending key {key}: {e}")

    def FindPic(self, template_path, x=None, y=None, width=None, height=None, threshold=0.8):
        """
        使用模板匹配在窗口截图中查找模板图像的位置。

        参数:
        template_path (str): 模板图像的文件路径。
        threshold (float, optional): 匹配阈值，范围为 0 到 1。默认为 0.8。
        x (int, optional): 截图区域左上角的 x 坐标。默认为 None，表示从窗口左上角开始。
        y (int, optional): 截图区域左上角的 y 坐标。默认为 None，表示从窗口左上角开始。
        width (int, optional): 截图区域的宽度。默认为 None，表示到窗口右下角。
        height (int, optional): 截图区域的高度。默认为 None，表示到窗口右下角。

        返回值:
        list: 包含匹配位置坐标的列表，格式为 [(x1, y1, width, height), ...]。如果未找到匹配，返回空列表。
        """
        try:
            # 读取模板图像
            template = cv2.imread(template_path, cv2.IMREAD_GRAYSCALE)
            if template is None:
                print(f"Error reading template image from {template_path}")
                return []

            # 捕获窗口图像
            screenshot = self.CaptureImg(x, y, width, height)
            if screenshot is None:
                print("Error capturing screenshot")
                return []

            # 转换为灰度图像
            screenshot_gray = cv2.cvtColor(screenshot, cv2.COLOR_BGR2GRAY)

            # 使用模板匹配
            res = cv2.matchTemplate(screenshot_gray, template, cv2.TM_CCOEFF_NORMED)
            min_val,max_val,min_loc,mac_loc=cv2.minMaxLoc(res)
            # 判断是否找到匹配
            if max_val < threshold:
                print(f"No match found. Best match score: {max_val}")
                return []

            # 存储匹配位置
            positions = []
            template_height, template_width = template.shape
            # 调整坐标为相对于限定区域的坐标
            adjusted_x = mac_loc[0] + (x if x is not None else 0)
            adjusted_y = mac_loc[1] + (y if y is not None else 0)
            # 修改为返回rect
            rect = (adjusted_x, adjusted_y, adjusted_x + template_width, adjusted_y + template_height)
            positions.append(rect)

            return positions
        except Exception as e:
            print(f"Error matching image: {e}")
            return []

    def FindPicBySift(self, template_path, x=None, y=None, width=None, height=None, threshold=0.8, template_scale=1.0, screenshot_scale=0.6):
        """
        使用 SIFT 特征匹配在窗口截图中查找模板图像的位置。

        参数:
        template_path (str): 模板图像的文件路径。
        x (int, optional): 截图区域左上角的 x 坐标。默认为 None，表示从窗口左上角开始。
        y (int, optional): 截图区域左上角的 y 坐标。默认为 None，表示从窗口左上角开始。
        width (int, optional): 截图区域的宽度。默认为 None，表示到窗口右下角。
        height (int, optional): 截图区域的高度。默认为 None，表示到窗口右下角。
        threshold (float, optional): 匹配阈值，范围为 0 到 1。默认为 0.75。阈值越小，匹配越模糊，错误匹配越多。
        template_scale (float, optional): 模板图像的缩放比例。默认为 1.0。
        screenshot_scale (float, optional): 窗口截图的缩放比例。默认为 1.0。

        返回值:
        list: 包含匹配位置坐标的列表，格式为 [(x1, y1, width, height), ...]。如果未找到匹配，返回空列表。
        """
        try:
            # 读取模板图像
            template = cv2.imread(template_path, cv2.IMREAD_GRAYSCALE)
            if template is None:
                print(f"Error reading template image from {template_path}")
                return []

            # 缩放模板图像
            template = cv2.resize(template, (int(template.shape[1] * template_scale), int(template.shape[0] * template_scale)))

            # 捕获窗口图像
            screenshot = self.CaptureImg(x, y, width, height)
            if screenshot is None:
                print("Error capturing screenshot")
                return []

            # 缩放窗口图像
            screenshot = cv2.resize(screenshot, (int(screenshot.shape[1] * screenshot_scale), int(screenshot.shape[0] * screenshot_scale)))
            # 转换为灰度图像
            screenshot_gray = cv2.cvtColor(screenshot, cv2.COLOR_BGR2GRAY)

            # 创建 SIFT 检测器
            sift = cv2.SIFT_create()

            # 检测关键点和描述符
            kp1, des1 = sift.detectAndCompute(template, None)
            kp2, des2 = sift.detectAndCompute(screenshot_gray, None)

            # 使用 FLANN 进行匹配
            FLANN_INDEX_KDTREE = 1
            index_params = dict(algorithm=FLANN_INDEX_KDTREE, trees=5)
            search_params = dict(checks=50)
            flann = cv2.FlannBasedMatcher(index_params, search_params)
            matches = flann.knnMatch(des1, des2, k=2)

            # 筛选出好的匹配
            good_matches = []
            for m, n in matches:
                if (n.distance-m.distance)==0 or (n.distance-m.distance)/n.distance >= threshold:
                    good_matches.append(m)

            if len(good_matches) == 0:
                print("No good matches found")
                return []

            # 提取匹配点的坐标
            positions = []
            dst_pts = np.float32([kp2[m.trainIdx].pt for m in good_matches]).reshape(-1, 2)

            # 计算匹配区域的包围矩形（考虑缩放比例）
            rx, ry, rw, rh = cv2.boundingRect(dst_pts)
            # 缩放坐标回原始截图尺寸
            rx = int(rx / screenshot_scale)
            ry = int(ry / screenshot_scale)
            rw = int(rw / screenshot_scale)
            rh = int(rh / screenshot_scale)
            # 调整到窗口坐标（考虑截图区域的偏移）
            adjusted_x = rx + (x if x is not None else 0)
            adjusted_y = ry + (y if y is not None else 0)
            # 格式化为 (x, y, width, height)
            rect = (adjusted_x, adjusted_y, adjusted_x+rw,adjusted_y+ rh)
            positions.append(rect)

        except Exception as e:
            print(f"Error matching image: {e}")
            return []
        return positions

